/*******************************************************************************
this code is protected by the GNU affero GPLv3
author:Sylvain BERTRAND <sylvain.bertrand AT gmail dot com>
*******************************************************************************/
#include <ulinux/compiler_types.h>
#include <ulinux/types.h>
#include <ulinux/error.h>
#include <ulinux/fs.h>
#include <ulinux/file.h>
#include <ulinux/dirent.h>
#include <ulinux/utils/mem.h>
#include <ulinux/utils/ascii/string/string.h>
#include <ulinux/sysc.h>

#include "out.h"
#include "ulinux_namespace.h"
#include "globals.h"

#define DIRENTS_BUF_SZ 8192

static u8 is_current(u8 *n)
{
  if(n[0]=='.'&&n[1]==0) return 1;
  return 0;
}

static u8 is_parent(u8 *n)
{
  if(n[0]=='.'&&n[1]=='.'&&n[2]==0) return 1;
  return 0;
}

static u8 is_newroot(u8 *n)
{
  #define NEWROOT (u8*)"newroot"
  return strncmp(n,NEWROOT,sizeof(NEWROOT)-1)?0:1;
}

static void unlink(i parent_fd,u8 *n,i flgs)
{
  l r=unlinkat(parent_fd,n,flgs);
  if(ISERR(r)){
    OUT("ERROR(%ld):unable to remove dir entry:%s\n",r,n);
    exit_group(-1);
  }
}

static void dir_del(i parent_fd);
static void dirent_del(i parent_fd,struct dirent64 *d)
{
  if(d->type==DT_DIR){
    if(!is_current(d->name)&&!is_parent(d->name)){
      i dir_fd;
      loop{
        dir_fd=(i)openat(parent_fd,d->name,ULINUX_O_RDONLY|ULINUX_O_NONBLOCK);
        if(dir_fd!=-EINTR) break;
      }
      if(ISERR(dir_fd)){
        OUT("ERROR(%d):unable to open subdir:%s\n",dir_fd,d->name);
        exit_group(-1);
      }else{
        l r;
        dir_del(dir_fd);
        loop{
          r=close(dir_fd);
          if(r!=-EINTR) break;
        }
        if(ISERR(r)){
          OUT("ERROR(%ld):unable to close dir fd\n",r);
          exit_group(-1);
        }
      }
      unlink(parent_fd,d->name,AT_REMOVEDIR);
    }
  }else unlink(parent_fd,d->name,0);
}

static void dir_del(i parent_fd)
{
  u8 dirents[DIRENTS_BUF_SZ];
  loop{
    l idx;
    l r=getdents64(parent_fd,dirents,DIRENTS_BUF_SZ);
    if(ISERR(r)){
      OUT("ERROR(%ld):getdents error\n",r);
      exit_group(-1);
    }

    if(!r) break;

    idx=0;
    loop{
      struct dirent64 *d;

      if(idx>=r) break;
      d=(struct dirent64*)(dirents+idx);
      dirent_del(parent_fd,d);
      idx+=d->rec_len;
    }
  }
}

void ramfs_cleanup(void)
{
  OUT(PRE "cleaning ramfs...");
  i root_fd;
  loop{
    root_fd=(i)open("/",ULINUX_O_RDONLY|ULINUX_O_NONBLOCK);
    if(root_fd!=-EINTR) break;
  }
  if(ISERR(root_fd)){
    OUT("ERROR(%d):unable to open root dir\n",root_fd);
    exit_group(-1);
  }

  u8 dirents[DIRENTS_BUF_SZ];
  loop{
    l idx;
    l r=getdents64(root_fd,dirents,DIRENTS_BUF_SZ);
    if(ISERR(r)){
      OUT("ERROR(%ld):getdents error\n",r);
      exit_group(-1);
    }

    if(!r) break;/*empty*/

    idx=0;
    loop{
      struct dirent64 *d;

      if(idx>=r) break;

      d=(struct dirent64*)(dirents+idx);
      if(!is_newroot(d->name)) dirent_del(root_fd,d);
      idx+=d->rec_len;
    }
  }
  l r;
  loop{
    r=close(root_fd);
    if(r!=-EINTR) break;
  }
  if(ISERR(r)){
    OUT("ERROR(%ld):unable to root dir fd\n",r);
    exit_group(-1);
  }
  OUT("done\n");
}
